<?php
/**
 * Core class of iLivGallery, give you everything you need to
 * open directories and parse for images.
 * 
 * features:
 * 	- renders png thumbs with transparent mirror
 * 	- renders pdf thumbs (not implemented)
 *
 * PHP 5
 *
 * MIT License
 * Copyright (c) 2009 Christian Krause
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @category   library
 * @package    iLivGallery
 * @author     Original Author <chris@christiankrause.de>
 * @copyright  2009 Christian Krause
 * @license    MIT
 * @link       http://code.google.com/p/ilivgallery/downloads/
 * @since      File available since Release 0.5
 */

require_once 'Debug.php';
require_once 'Config.php';
require_once 'Setup.php';
require_once 'File.php';

class iLiv_Core {

	/**
	 * @var iLiv_Setup
	 */
	protected	$setup;
	/**
	 * @var iLiv_Debugger
	 */
	protected	$debugger; //TODO: implement Zend Debugger
	/*
	 * @var iLivFile
	 */
	protected 	$curFile;
	/*
	 * @var string
	 */
	protected	$sCurThumbName;
	/*
	 * @var resource
	 */
	protected 	$thumbHandle;
	/**
	 * path to current Gallery
	 * 
	 * @var string
	 */
	protected	$sCurGallery;
	/**
	 * path to current Gallery thumbs
	 * 
	 * @var string
	 */
	protected	$sCurThumbs;
	/**
	 * Do blacklisting here.
	 * @var array
	 */
	protected 	$aExcludedContent	= array ( '.', '..', '.thumb' ); //TODO: change against white listing
	/**
	 * currently just jpeg
	 * 
	 * @var array
	 */
	protected	$aAllowedMime		= array ( 'image/jpeg', 'application/pdf' );
	/**
	 * array with iLiv_File images
	 * 
	 * @var array
	 */
	public		$aImagesStack;

	/**
	 * this var holds an hook call stack.
	 * looks like: array('name' => 'myCoolHookInFunc', args => array(1,2,...,30))
	 * @var array
	 */
	protected 	$aHooks;

	/**
	 * Init debugger and setup
	 * 
	 * @param $debugger iLiv_Debugger
	 * @param $setup iLiv_Setup
	 * @return iLiv_Core
	 */
	public function __construct( iLiv_Debugger $debugger, iLiv_Setup $setup ) { //strategy pattern

		$this->debugger	= $debugger;	//set global debugger here!
		$this->setup	= $setup;		// instance of setup

		return $this;
	}

	/**
	 * open a file and read its informations
	 * into an array.
	 *
	 * @return iLiv_File
	 */
	protected function setCurFile($fileName) {

		$this->curFile = iLiv_File::getInstance($this->sCurGallery.
									  		   		DIRECTORY_SEPARATOR.
													$fileName)
													->getImageInfos();

		return $this;
	}

	
	/**
	 * adds an image to the aImagesStack
	 * 
	 * @param $image iLiv_File
	 * @return iLiv_Core
	 */
	protected function setImage(iLiv_File $image) {

		$this->aImagesStack[] = $image;

		return $this;
	}

	/**
	 * set the name of thumbfile, if needed set some
	 * extra scale sizes
	 * 
	 * @param $extraScale
	 * @return iLiv_Core
	 */
	protected function setThumbName() {

		$this->sCurThumbName = $this->getCurFile()->basename.'_'.$this->scale.'.png';

		return $this;
	}

	/**
	 * Read all images of an directory by giving the path parm,
	 * also make thumbs if $this->setup->getCfgValueByKey('img_thumbs')
	 * is enabled.
	 *
	 * @param $sDirPath string
	 * @return array
	 */
	protected function getImagesOfDir( $sDirPath = '' ) {

		$curGalleryPath = rtrim($this->setup->sysGalleryPath.$sDirPath, '/');

		if ( !is_dir($curGalleryPath) )
		{
			$this->debugger->setError( 'core',
									    __METHOD__,
									    'Unable to open: '.$curGalleryPath);
			return false;
		}
		else
		{
			$this->sCurGallery = $curGalleryPath;
			$this->sCurThumbs  = $curGalleryPath.
									   DIRECTORY_SEPARATOR.
									   'thumbs';
		}

		/**
		* Open and parse directory
		*/

		$dirHandle = opendir($this->sCurGallery);

		while ( $sDirContent = readdir($dirHandle) )
		{
			if ( !in_array($sDirContent, $this->aExcludedContent) &&
				 is_file($this->sCurGallery.
				 		 DIRECTORY_SEPARATOR.
				 		 $sDirContent) )
			{
				$this->setCurFile($sDirContent);

				if ( $this->isAllowedMime() )
				{
					foreach( $this->getConfigVal('img_scale') as $scale )
					{
						$this->setScale($scale)
							 ->doHookIn()
							 ->doThumbsHandle();
					}
					$this->setImage($this->curFile);
				}
			}
		}//while

	    return $this->aImagesStack;
	}

	/**
	 * Sets render scale
	 * @param float $scale
	 * @return iLiv_Core
	 */
	protected function setScale($scale=0) {

		$this->scale = $scale;
		$this->getCurFile()->setScaleDown($scale);

		return $this;
	}

	/**
	 * execute the callbacks you hooked in.
	 * @return iLiv_Core
	 */
	protected function doHookIn() {

		if (!is_array($this->aHooks) )
		{
			return;
		}

		foreach ( $this->aHooks as $callArray )
		{
			switch($callArray['type'])
			{
				default:
				case 0:
					call_user_func_array($callArray['name'], $callArray['args']);
				break;
				case 1:
					call_user_func(array($this, $callArray['name']), $callArray['args']);
				break;
			}
		}

		return $this;
	}

	/**
	 * here you can plugin some functionality, all methods or functions
	 * you setup here will be called @getImagesOfDir.
	 * type:
	 *   0 = function
	 *   1 = method
	 * @param $sName string
	 * @param $aArgs array
	 * @param $type int
	 * @return void
	 */
	public function setHookIn($sName, $aArgs=array(), $type=0) {

		$this->aHooks[] = array('name' => $sName,
								'args' => $aArgs,
								'type' => (int)$type);
	}

	/**
	 * shortcut to set config value
	 * @return iLiv_Config
	 */
	protected function setConfigVal($key, $value) {
		return $this->getSetup()->sysConfig->setValue($key, $value);
	}

	/**
	 * shortcut to get config value
	 * @return iLiv_Config
	 */
	protected function getConfigVal($key) {
		return $this->getSetup()->getCfgValueByKey($key);
	}

	/**
	 * current file getter
	 * @return iLiv_File
	 */
	protected function getCurFile() {
		return $this->curFile;
	}

	/**
	 * setup getter
	 * @return iLiv_Setup
	 */
	protected function getSetup() {
		return $this->setup;
	}

	/**
	 * just get a secure raw url encoded string of the
	 * filename 
	 * @return string
	 */
	protected function getThumbOutputName() {
		return rawurlencode($this->curFileBaseName.'.png');
	}

	/**
	 * root method. It generates a thumb, the handle and makes reflaction
	 * if needed. also checks the file creation time.
	 * @return iLiv_Core
	 */
	protected function doThumbsHandle() {

		$this->setThumbName();

		if ( $this->checkThumbDir() === false )
		{
			$this->mkThumbDir();
		}

		if ( $this->thumbImageExists() === false ||
			 $this->fileIsNew() )
		{								   					//if thumbs wanted and there is no thumbfile
			if ( $this->mkImageThumb() ) //first of it all scale down and make a image handle
			{
				if ( $this->setup->getCfgValueByKey('thumbs_mirror') ) //if mirror is want, do mirror
				{							   //but dont for extraScale
					$this->mkImageReflection();
				}

				if ( is_resource($this->thumbHandle) )
				{
					//@TODO: should possible to save in other formats than png
					imagepng($this->thumbHandle,
							 $this->sCurThumbs.DIRECTORY_SEPARATOR.$this->sCurThumbName); //save only as png files (because the transparent mirror)
					imagedestroy($this->thumbHandle);//cleanup img object
				}
			}
		}

		return $this;
	}

	protected function isAllowedMime() {

		if ( in_array($this->getCurFile()->mime_by_suffix, $this->aAllowedMime) )
		{
			return true;
		}

		return false;
	}

	protected function checkThumbDir() {

		if ( is_dir($this->sCurThumbs) )
		{
			return true;
		}

		return false;
	}

	protected function mkThumbDir() { //TODO: Problem in create folder

		if ( @mkdir($this->sCurThumbs) === false )
		{
			$this->debugger->setError('class.core', __METHOD__, 'Could not create thumbnail directory.');
			return false;
		}

		return true;
	}

	protected function thumbImageExists() {

		#if thumb is older than org. image, we have this thumb
		if ( file_exists($this->sCurThumbs.DIRECTORY_SEPARATOR.$this->sCurThumbName) &&
			 filemtime($this->sCurThumbs.DIRECTORY_SEPARATOR.$this->sCurThumbName) >=
			 filemtime($this->sCurGallery.DIRECTORY_SEPARATOR.$this->curFile->name) )
		{	//check filemtime (is org. file older than thumb? else make new thumb)
			return true;
		}

		return false;
	}

	protected function fileIsNew() {

		#if thumb is older than config, we have to renew all thumbs
		if ( file_exists($this->sCurThumbs.DIRECTORY_SEPARATOR.$this->sCurThumbName) &&
			 $this->setup->getLastCfgDate() >=
			 filemtime($this->sCurThumbs.DIRECTORY_SEPARATOR.$this->sCurThumbName) )
		{
	 		return true;
		}

		return false;
	}

	protected function mkImageThumb() {
		
		switch($this->getCurFile()->mime_by_suffix)
		{
			case 'image/jpeg':
				return $this->mkJpegThumb();
			break;
			case 'application/pdf':
				return $this->mkPdfThumb();//false;
			break;
		}

	}

	protected function mkJpegThumb() {

		$src = imagecreatefromjpeg($this->curFile->path);

		$size = getimagesize($this->curFile->path);

		if ( $size[0] > $this->scale ||
			 $size[1] > $this->scale )
		{ 					//if height OR width is more than scale
			$wdim      = $size[0] / $this->scale;			//how much times the image is bigger as it should to be
			$newHeight = $size[1] / $wdim; //new height
			$newWidth  = $this->scale;

			if ( $newHeight > $this->scale )
			{
				$hdim      = $newHeight / $this->scale;
				$newWidth  = $this->scale / $hdim;
				$newHeight = $this->scale;
			}

			$width  = round($newWidth);
			$height = round($newHeight);
		}
		else
		{
			$width  = round($size[0]);
			$height = round($size[1]);
		}

		$this->thumbHandle = imagecreatetruecolor($width,$height);
		imagecopyresampled($this->thumbHandle,
						   $src,0,0,0,0,$width,$height,$size[0],$size[1]);

		return true;
	}
	
	protected function mkPdfThumb() {

		return false;

		if ( $frames > 0 )
		{
			$sourceFrames = '[0-'.$frames.']'; //decremental because we count from 0
		}

		$parms = array(
			escapeshellarg($this->sCurGallery.DIRECTORY_SEPARATOR.$this->curFile->name.$sourceFrames), //source
			'-rotate '.escapeshellarg('-90 < '.$this->scale),
			'-scale '.escapeshellarg($this->scale),
//			$thumbFrame,
			escapeshellarg($this->sCurThumbs.DIRECTORY_SEPARATOR.$this->sCurThumbName), //target
		);

		$last_line = system('convert '.implode(' ', $parms), $retval); //put it into the unix pipe
		$this->debugger->setError(__CLASS__, __METHOD__, 'convert '.implode(' ', $parms), $retval);
		//@TODO: Put thumb into the file handle for further more transactions
//		var_dump($retval);
//		exit;

	}

	protected function mkImageReflection() {

		$src_height = imagesy($this->thumbHandle);
		$src_width = imagesx($this->thumbHandle);
		$dest_height = $src_height + ($src_height / 2);
		$dest_width = $src_width;

		$oMirrorImg = imagecreatetruecolor($dest_width, $dest_height);
		imagealphablending($oMirrorImg, false);
		imagesavealpha($oMirrorImg, true);

		imagecopy($oMirrorImg, $this->thumbHandle, 0, 0, 0, 0, $src_width, $src_height);
		$reflection_height = $src_height / 2;
		$alpha_step = 80 / $reflection_height;

		for ( $y = 1; $y <= $reflection_height; $y++ )
		{
			for ( $x = 0; $x < $dest_width; $x++ )
			{
				// copy pixel from x / $src_height - y to x / $src_height + y
				$rgba = imagecolorat($this->thumbHandle, $x, $src_height - $y);
				$alpha = ($rgba & 0x7F000000) >> 24;
				$alpha =  max($alpha, 47 + ($y * $alpha_step));
				$rgba = imagecolorsforindex($this->thumbHandle, $rgba);
				$rgba = imagecolorallocatealpha($oMirrorImg, $rgba['red'], $rgba['green'], $rgba['blue'], $alpha);
				imagesetpixel($oMirrorImg, $x, $src_height + $y - 1, $rgba);
			}
		}

		$this->thumbHandle = $oMirrorImg;

		return true;
	}

}
